package com.numericalmethod.algoquant.model.ralph2009;

import java.io.Serializable;
import java.util.Map;
import java.util.TreeMap;

/**
 * 
 * This class encapsulates all the outputs from the <code>Ralph2009PortfolioOptimizer</code>
 * 
 * 
 * @author Paul/Clement/Stephen
 *
 */
public class Ralph2009OptimizedPortfolio implements Serializable {

	private static final long serialVersionUID = 2921157942243326773L;

	// mapping from time step index (0 is current trade date) to the portfolio weight allocated to the risky asset
	private Map<Integer, Double> dynamicWeights;
	private Map<Integer, Double> myopicWeights;
	//correlation between psiT+1 and excessReturnT+1, this indicate how much dynamicWeights should deviate from myopicWeights
	private Map<Integer, Double> correlations; 
	
	private Ralph2009PortfolioConstraint constraint;


	/**
	 * Create a Ralph2009OptimizedPortfolio with no constraint.
	 */
	public Ralph2009OptimizedPortfolio() {
		this(null);
	}

	/**
	 * Create a Ralph2009OptimizedPortfolio with a given constraint such as shortselling/leveraging.
	 */
	public Ralph2009OptimizedPortfolio(Ralph2009PortfolioConstraint constraint) {
		dynamicWeights = new TreeMap<Integer, Double>();
		myopicWeights = new TreeMap<Integer, Double>();
		correlations =  new TreeMap<Integer, Double>();
		
		this.constraint=constraint;
	}

	
	public double getDynamicWeightForTrading() {
		if (!dynamicWeights.containsKey(0))
			throw new IllegalStateException("No dynamic weights for time index 0");
		return dynamicWeights.get(0);
	}

	public double getMyopicWeightForTrading() {
		if (!myopicWeights.containsKey(0))
			throw new IllegalStateException("No myopic weights for time index 0");
		return myopicWeights.get(0);
	}
	
	public double getCorrelationForTrading() {
		if (!correlations.containsKey(0))
			throw new IllegalStateException("No correlations for time index 0");
		return correlations.get(0);
	}

	/**
	 * Specify the portfolio constraint such as allowing shortselling/leveraging
	 * 
	 */
	public void setConstraint(Ralph2009PortfolioConstraint constraint) {
		this.constraint = constraint;
	}
	
	
	public void addDynamicWeightResult(int timestep, double weight) {
		
		dynamicWeights.put(timestep, applyConstraints(weight));
	}
	
	public void addMyopicWeightResult(int timestep, double weight) {
		myopicWeights.put(timestep, applyConstraints(weight));
	}
	
	public void addCorrelation(int timestep, double corr) {
		correlations.put(timestep, corr);
	}
	
	public double getDynamicWeightAt(int timestep) {
		return dynamicWeights.get(timestep);
	}

	public double getMyopicWeightAt(int timestep) {
		return myopicWeights.get(timestep);
	}

	public double getCorrelationAt(int timestep) {
		return correlations.get(timestep);
	}
	
	public int getDynamicWeightsCount() {
		return dynamicWeights.size();
	}
	
	public int getMyopicWeightsCount() {
		return myopicWeights.size();
	}
	

	/**
	 * Get the string presentation of the optimized portfolio.
	 * It prints out all portfolio dynamic and myopic weights of each simulation time step.
	 * 
	 */
	@Override
	public String toString() {
		StringBuilder buf=new StringBuilder(3000);
		buf.append("Ralph2009OptimizedPortfolio\n");
		buf.append("---- Dynamic Weights ----\n");
		for (Map.Entry<Integer, Double> en : dynamicWeights.entrySet()) {
			buf.append("t = ");
			buf.append(en.getKey());
			buf.append(", weight = ");
			buf.append(en.getValue());
			buf.append("\n");
		}
		buf.append("\n---- Myopic Weights ----\n");
		for (Map.Entry<Integer, Double> en : myopicWeights.entrySet()) {
			buf.append("t = ");
			buf.append(en.getKey());
			buf.append(", weight = ");
			buf.append(en.getValue());
			buf.append("\n");
		}
		buf.append("-------------------------\n");
		return buf.toString();
	}

	/**
	 * Internal adjustment on the result weighting based on the constraints specified.
	 * @param weight The original weight given by the optimization algorithm
	 * @return The actual result portfolio weight after considering all the constraints.
	 */
	private double applyConstraints(double weight) {

		if (constraint!=null) {
		
			if (!constraint.isAllowShortsell() && weight<0.0)
				weight=0.0;
			
			if (!constraint.isAllowLeverage() && Math.abs(weight)>1.0) 
				weight /= Math.abs(weight); // rescale to 1.0 and preserve the sign.
		}
		
		return weight;
	}

	
}
